package sentinel.springboot.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;

@RestController
@RequestMapping("/demo")
public class DemoController {

    @GetMapping("/echo")
    public String echo() {
        return "echo";
    }

    @GetMapping("/test")
    public String test() {
        return "test";
    }

    @GetMapping("/sleep")
    public String sleep() throws InterruptedException {
        Thread.sleep(100L);
        return "sleep";
    }

    /**
     * 对热点数据进行限流
     * 为什么不直接使用 sentinel-spring-webmvc-adapter 库，
     * 自动给该 demo/product_info 接口生成的 GET:/demo/product_info 呢？
     * 原因：因为 sentinel-spring-webmvc-adapter 库提供的 
     *      SentinelWebInterceptor 和 SentinelWebTotalInterceptor 拦截器在调用 Sentinel 客户端时，
     *      并未传入参数，所以无法进行热点参数限流。
     * @param id
     * @return
     */
    @GetMapping("/product_info")
    @SentinelResource("demo_product_info_hot")
    public String produceInfo(Integer id) {
        return "商品编号" + id;
    }

    @GetMapping("entry_demo")
    public String entryDemo() {
        Entry entry = null;
        // 1. 访问资源 调用 Sentinel 的 SphU#entry(String name) 方法，访问资源
        try {
            entry = SphU.entry("entry_demo");
            // 2. 执行业务逻辑
            // 手动进行资源的保护。但是我们会发现，对代码的入侵太强，需要将业务逻辑进行修改。
            // 因此，Sentinel 提供了 @SentinelResource 注解声明自定义资源，
            // 通过 Spring AOP 拦截该注解的方法，自动调用 Sentinel 客户端 API，进行指定资源的保护。
            // 注解方式埋点不支持 private 方法。

            return "执行成功";
        } catch (BlockException blockException) {
            blockException.printStackTrace();
            // 3. blockException
            return "访问被拒绝";
        } finally {
            // 4. 释放资源 调用 Sentinel 的 Entry#exit() 方法，释放对资源的访问。
            // 注意，entry 和 exit 必须成对出现，不然资源一直被持有者。
            if (entry != null) {
                entry.exit();
            }
        }
    }

    @GetMapping("/anno_demo")
    @SentinelResource(value = "anno_demo_resource", blockHandler = "blockHandler", fallback = "fallback")
    public String annotationsDemo(@RequestParam(required = false) Integer id) throws Exception {
        if (id == null) {
            throw new IllegalArgumentException("id参数不能为空");
        }

        return "success.....";
    }

    /**
     * blockHandler处理函数 最后一个参数必须是BlockException
     * @param id id
     * @param ex BlockException
     * @return
     */
    public String blockHandler(Integer id, BlockException ex) {
        return "block:" + ex.getClass().getSimpleName();
    }

    /**
     * fallback 函数名称，可选项，用于在抛出异常的时候提供 fallback 处理逻辑。
     * fallback 函数可以针对所有类型的异常（除了exceptionsToIgnore：里面排除掉的异常类型）进行处理。
     * fallback 函数签名和位置要求： 返回值类型必须与原函数返回值类型一致；方法参数列表需要和原函数一致，或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
     * fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数，则可以指定 fallbackClass 为对应的类的 Class 对象，注意对应的函数必需为 static 函数，否则无法解析。
     * @param id
     * @param throwable
     * @return
     */
    public String fallback(Integer id, Throwable throwable) {
        return "fallback：" + throwable.getMessage();
    }
}